﻿/*This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>
	*/

using System;
using System.Text;
using System.IO;

namespace ZipLib
{
    /// <summary>
    /// Central entry header with more details.
    /// </summary>
    public class ZipCentralDirectoryHeader : ZipEntryHeader
    {
        /// <summary>
        /// PKZip version needed to extract
        /// </summary>
        public short VersionNeeded { get; set; }

        /// <summary>
        /// The length of file comment
        /// </summary>
        public short FileCommentLength { get; private set; }

        /// <summary>
        /// The number of the disk on which this file exists.
        /// </summary>
        public short DiskStart { get; private set; }

        /// <summary>
        /// Internal file attributes.
        /// </summary>
        public short InternalAttribute { get; private set; }

        /// <summary>
        /// External file attributes, host system dependent.
        /// </summary>
        public int ExtranalAttribute { get; set; }

        /// <summary>
        /// Relative offset of local header. This is the offset of where to find the corresponding local file header from the start of the first disk.
        /// </summary>
        protected internal int LocalHeaderOffset { get; set; }

        /// <summary>
        /// An optional comment for the file.
        /// </summary>
        public string FileComment
        {
            get { return _fileComment; }
            set
            {
                _fileComment = value;
                FileCommentLength = (short) value.Length;
            }
        }

        private string _fileComment;
        protected long LocalHeaderPosition;

        /// <summary>
        /// Resource stream (compressed).
        /// </summary>
        protected internal Stream ResourceStream;

        /// <summary>
        /// Initialize a <see cref="ZipCentralDirectoryHeader"/> with <see cref="System.IO.BinaryReader"/>
        /// </summary>
        /// <param name="r"><see cref="System.IO.BinaryReader"/> containing zip stream</param>
        public ZipCentralDirectoryHeader(BinaryReader r)
        {
            if (r.ReadInt32() != 0x02014B50) throw new ZipException("Incorrect central center header");
            Version = r.ReadInt16();
            VersionNeeded = r.ReadInt16();
            Flags = r.ReadInt16();
            Compression = r.ReadInt16();
            ModTime = r.ReadInt16();
            ModDate = r.ReadInt16();
            Crc32 = r.ReadUInt32();
            CompressedSize = r.ReadUInt32();
            UncompressedSize = r.ReadUInt32();
            FileNameLength = r.ReadUInt16();
            ExtraFieldLength = r.ReadUInt16();
            FileCommentLength = r.ReadInt16();
            DiskStart = r.ReadInt16();
            InternalAttribute = r.ReadInt16();
            ExtranalAttribute = r.ReadInt32();
            LocalHeaderOffset = r.ReadInt32();
            FileName = Encoding.ASCII.GetString(r.ReadBytes(FileNameLength));
            ExtraFiled = r.ReadBytes(ExtraFieldLength);
            FileComment = Encoding.ASCII.GetString(r.ReadBytes(FileCommentLength));
            var tempPos = r.BaseStream.Position;
            r.BaseStream.Position = LocalHeaderOffset;
            var temp = new ZipEntryHeader(r);
            StreamPosition = temp.StreamPosition;
            r.BaseStream.Position = tempPos;
        }

        /// <summary>
        /// Initialize a new instance of <see cref="ZipCentralDirectoryHeader"/>/>
        /// </summary>
        public ZipCentralDirectoryHeader() : this("")
        {
        }

        /// <summary>
        /// Initialize a new instance of <see cref="ZipCentralDirectoryHeader"/> with given name
        /// </summary>
        /// <param name="filename">FileName of the entry. Use "/" to separate directory</param>
        public ZipCentralDirectoryHeader(string filename) : this(null, filename)
        {
        }

        /// <summary>
        /// Initialize a new instance of <see cref="ZipCentralDirectoryHeader"/> with given name and stream
        /// </summary>
        /// <param name="s">Uncompressed stream</param>
        /// <param name="filename">FileName of the entry. Use "/" to separate directory</param>
        public ZipCentralDirectoryHeader(Stream s, string filename) : this(s, filename, "")
        {
        }

        /// <summary>
        /// Initialize a new instance of <see cref="ZipCentralDirectoryHeader"/> with given name, stream, and file comment
        /// </summary>
        /// <param name="resource">Uncompressed stream</param>
        /// <param name="filename">FileName of the entry. Use "/" to separate directory</param>
        /// <param name="filecomment">File comment with the entry file</param>
        public ZipCentralDirectoryHeader(Stream resource, string filename, string filecomment)
        {
            Version = 0x14;
            VersionNeeded = 0x14;
            Flags = 0x00;
            Compression = 0x08;
            ModDateTime = DateTime.Now;

            if (resource != null)
            {
                UncompressedSize = (uint) resource.Length;
                var crc = new Crc32();
                Crc32 = crc.CalculateCrc32(resource, out ResourceStream);
                CompressedSize = (uint) ResourceStream.Length;
            }
            else
            {
                UncompressedSize = 0;
                UncompressedSize = 0;
                Crc32 = 0;
            }
            FileName = filename;
            FileComment = filecomment;
            ExtraFiled = new byte[0];
            DiskStart = 0;
        }

        protected internal void Parse(BinaryWriter w)
        {
            w.Write(0x02014B50);
            w.Write(Version);
            w.Write(VersionNeeded);
            w.Write(Flags);
            w.Write(Compression);
            w.Write(ModTime);
            w.Write(ModDate);
            w.Write(Crc32);
            w.Write(CompressedSize);
            w.Write(UncompressedSize);
            w.Write(FileNameLength);
            w.Write(ExtraFieldLength);
            w.Write(FileCommentLength);
            w.Write(DiskStart);
            w.Write(InternalAttribute);
            w.Write(ExtranalAttribute);
            w.Write(LocalHeaderOffset);
            w.Write(Encoding.ASCII.GetBytes(FileName));
            w.Write(ExtraFiled);
            w.Write(Encoding.ASCII.GetBytes(FileComment));
            StreamPosition = w.BaseStream.Position;
        }
    }

    /// <summary>
    /// Entry type
    /// </summary>
    public enum EntryType
    {
        File,
        Directory
    }
}